package com.refactoring.chapter01;

import java.text.NumberFormat;
import java.util.*;
import java.util.stream.Collectors;

public class Invoice {
    private String customer;
    private List<Performance> performances;

    private List<Play> plays;

    private int totalVolumeCredits;

    private int totalAmount;

    public Invoice(String customer, List<Performance> performances, List<Play> plays) {
        this.customer = customer;
        this.performances = performances;
        this.plays = plays;
    }

    private String usd(int totalAmount) {
        Locale locale = new Locale("en", "US");
        NumberFormat format = NumberFormat.getCurrencyInstance(locale);
        Currency currency = Currency.getInstance("USD");
        format.setCurrency(currency);
        format.setMinimumFractionDigits(2);
        return format.format(totalAmount / 100);
    }

    public String statement() {
        createStatementData();
        return renderPlainText();
    }

    public String htmlStatement() {
        createStatementData();
        return renderHtml();
    }

    private String renderHtml() {
        StringBuilder result = new StringBuilder("<h1>Statement for " + this.customer + "</h1>\n");
        result.append("<table>\n");
        result.append("<tr><th>play</th><th>seats</th><th>cost</th></tr>");
        for (Performance perf : this.performances) {
            result.append(" <tr><td>").append(perf.getPlay().getName()).append("</td><td>").append(perf.getAudience())
                    .append("</td>");
            result.append("<td>").append(this.usd(perf.getAmount())).append("</td></tr>");
        }
        result.append("</table>\n");
        result.append("<p>Amount owed is <em>").append(this.usd(this.totalAmount)).append("</em></p>\n");
        result.append("<p>You earned  <em>").append(this.totalVolumeCredits).append("</em> credit</p>\n");
        return result.toString();
    }

    private void createStatementData() {
        this.performances = this.performances.stream().map(this::enrichPerformance).collect(Collectors.toList());
        this.totalVolumeCredits = totalVolumeCredits();
        this.totalAmount = totalAmount();
    }

    public Performance enrichPerformance(Performance performance) {
        Performance result = performance.deepCopy(performance);
        PerformanceCalculator calculator = createPerformanceCalculate(result, playFor(result));
        result.setPlay(calculator.getPlay());
        result.setAmount(calculator.amount());
        result.setVolumeCredits(calculator.volumeCredits());
        return result;
    }

    private PerformanceCalculator createPerformanceCalculate(Performance performance, Play play) {
        switch (play.getType()) {
            case "tragedy": return new TragedyCalculator(performance, play);
            case "comedy": return new ComedyCalculator(performance, play);
            default:
                throw new RuntimeException("unknown type:" + play.getType());
        }
    }

    private String renderPlainText() {
        StringBuilder result = new StringBuilder("Statement for " + this.customer + "\n");
        for (Performance perf : this.performances) {
            result.append(perf.getPlay().getName()).append(": ").append(this.usd(perf.getAmount())).append(" (").append(perf.getAudience()).append(" seats)\n");
        }
        result.append("Amount owed is ").append(this.usd(this.totalAmount)).append("\n");
        result.append("You earned ").append(this.totalVolumeCredits).append(" credit\n");
        return result.toString();
    }


    private int totalAmount() {
        return this.performances.stream().mapToInt(Performance::getAmount).reduce(0, Integer::sum);
    }

    private int totalVolumeCredits() {
        return this.performances.stream().mapToInt(Performance::getVolumeCredits).reduce(0, Integer::sum);
    }

    private Play playFor(Performance perf) {
        Optional<Play> result = this.plays.stream().filter(play -> play.getPlayId().equals(perf.getPlayId())).findFirst();
        if (result.isPresent()) {
            return result.get();
        }
        throw new RuntimeException("play id is not exist");
    }
}
